package com.zusmart.core.config.support;

import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import com.zusmart.base.logging.Logger;
import com.zusmart.base.logging.LoggerFactory;
import com.zusmart.base.util.GenericUtils;
import com.zusmart.base.util.StringUtils;
import com.zusmart.core.config.ConfigData;
import com.zusmart.core.config.ConfigDataConverter;
import com.zusmart.core.config.ConfigDataType;
import com.zusmart.core.config.ConfigName;
import com.zusmart.core.config.ConfigSource;
import com.zusmart.core.config.ConfigSourceConverter;
import com.zusmart.core.config.ConfigSourceManager;

public abstract class AbstractConfigSourceManager implements ConfigSourceManager {

	private static final Logger logger = LoggerFactory.getLogger(AbstractConfigSourceManager.class);

	private static int NULL_VALUE_INT;
	private static short NULL_VALUE_SHORT;
	private static long NULL_VALUE_LONG;
	private static double NULL_VALUE_DOUBLE;
	private static float NULL_VALUE_FLOAT;
	private static boolean NULL_VALUE_BOOLEAN;
	private static char NULL_VALUE_CHAR;
	private static byte NULL_VALUE_BYTE;

	protected final Map<String, ConfigSource> configSourceMapping = new ConcurrentHashMap<String, ConfigSource>();
	protected final Map<String, ConfigSourceConverter> configSourceConverterMapping = new ConcurrentHashMap<String, ConfigSourceConverter>();
	protected final Map<Class<?>, ConfigDataConverter<?>> configDataConverterMapping = new ConcurrentHashMap<Class<?>, ConfigDataConverter<?>>();
	
	@Override
	public void attachConfigSource(ConfigSource configSource) {
		if (null == configSource) {
			return;
		}
		String configSourceKey = this.formatConfigSourceKey(configSource);
		if (this.configSourceMapping.containsKey(configSourceKey)) {
			return;
		}
		this.configSourceMapping.put(configSourceKey, configSource);
		logger.debug("attach config source success ({})", configSourceKey);
	}

	@Override
	public void attachConfigSource(InputStream inputStream, String fileName, String fileExtension) {
		if (null == inputStream) {
			throw new IllegalArgumentException("inputstream must not be null");
		}
		if (StringUtils.isBlank(fileName)) {
			throw new IllegalArgumentException("file name must not be blank");
		}
		if (StringUtils.isBlank(fileExtension)) {
			throw new IllegalArgumentException("file extension must not be blank");
		}
		if (!this.configSourceConverterMapping.containsKey(fileExtension)) {
			logger.warn("unsupport config source converter for {}.{}", fileName, fileExtension);
			return;
		}
		if (fileName.indexOf(fileExtension) != -1) {
			fileName = fileName.substring(0, fileName.indexOf(fileExtension) - 1);
		}
		String configSourceKey = this.formatConfigSourceKey(fileName, fileExtension);
		if (this.configSourceMapping.containsKey(configSourceKey)) {
			return;
		}
		try {
			ConfigSource configSource = this.configSourceConverterMapping.get(fileExtension).convert(inputStream, fileName);
			if (null == configSource) {
				logger.warn("config source convert failed {}", configSourceKey);
			}
			this.configSourceMapping.put(configSourceKey, configSource);
			logger.debug("attach config source success {}.{}", fileName, fileExtension);
		} catch (Exception e) {
			logger.warn("config source convert failed {}", e.getMessage());
		}
	}

	@Override
	public void attachConfigSourceConverter(ConfigSourceConverter configSourceConverter) {
		if (null == configSourceConverter) {
			return;
		}
		if (this.configSourceConverterMapping.containsKey(configSourceConverter.getSupportExtension())) {
			return;
		}
		this.configSourceConverterMapping.put(configSourceConverter.getSupportExtension(), configSourceConverter);
		logger.debug("attach config source converter success ({})", configSourceConverter.getSupportExtension());
	}

	@Override
	public void attachConfigDataConverter(ConfigDataConverter<?> configDataConverter) {
		if (null == configDataConverter) {
			return;
		}
		if (this.configDataConverterMapping.containsKey(configDataConverter.getSupportClass())) {
			return;
		}
		this.configDataConverterMapping.put(configDataConverter.getSupportClass(), configDataConverter);
		logger.debug("attach config data converter success ({})", configDataConverter.getSupportClass().getName());
	}

	@Override
	public Map<String, ConfigSource> getConfigSourceMapping() {
		return Collections.unmodifiableMap(this.configSourceMapping);
	}

	@Override
	public Map<String, ConfigSourceConverter> getConfigSourceConverterMapping() {
		return Collections.unmodifiableMap(this.configSourceConverterMapping);
	}

	@Override
	public Map<Class<?>, ConfigDataConverter<?>> getConfigDataConverterMapping() {
		return Collections.unmodifiableMap(this.configDataConverterMapping);
	}

	@Override
	public boolean isSupportConfigDataType(Class<?> configDataType) {
		if (null == configDataType) {
			return false;
		}
		if (configDataType.isPrimitive()) {
			return true;
		}
		return this.configDataConverterMapping.containsKey(configDataType);
	}

	@Override
	public ConfigData<Integer> getIntegerConfigData(String configName, Integer defaultData) {
		return this.getConfigData(Integer.class, configName, defaultData);
	}

	@Override
	public ConfigData<Integer> getIntegerConfigData(String configName) {
		return this.getIntegerConfigData(configName, null);
	}

	@Override
	public ConfigData<String> getStringConfigData(String configName, String defaultData) {
		return this.getConfigData(String.class, configName, defaultData);
	}

	@Override
	public ConfigData<String> getStringConfigData(String configName) {
		return this.getStringConfigData(configName, null);
	}

	@Override
	public ConfigData<Boolean> getBooleanConfigData(String configName, Boolean defaultData) {
		return this.getConfigData(Boolean.class, configName, defaultData);
	}

	@Override
	public ConfigData<Boolean> getBooleanConfigData(String configName) {
		return this.getBooleanConfigData(configName, null);
	}

	@Override
	public ConfigData<Short> getShortConfigData(String configName, Short defaultData) {
		return this.getConfigData(Short.class, configName, defaultData);
	}

	@Override
	public ConfigData<Short> getShortConfigData(String configName) {
		return this.getShortConfigData(configName, null);
	}

	@Override
	public ConfigData<Long> getLongConfigData(String configName, Long defaultData) {
		return this.getConfigData(Long.class, configName, defaultData);
	}

	@Override
	public ConfigData<Long> getLongConfigData(String configName) {
		return this.getLongConfigData(configName, null);
	}

	@Override
	public ConfigData<Double> getDoubleConfigData(String configName, Double defaultData) {
		return this.getConfigData(Double.class, configName, defaultData);
	}

	@Override
	public ConfigData<Double> getDoubleConfigData(String configName) {
		return this.getDoubleConfigData(configName, null);
	}

	@Override
	public ConfigData<Float> getFloatConfigData(String configName, Float defaultData) {
		return this.getConfigData(Float.class, configName, defaultData);
	}

	@Override
	public ConfigData<Float> getFloatConfigData(String configName) {
		return this.getFloatConfigData(configName, null);
	}

	@Override
	public <T> ConfigData<T> getConfigData(Class<T> dataType, String configName) {
		return this.getConfigData(dataType, configName, null);
	}

	@Override
	public <T> ConfigData<T> getConfigData(Class<T> dataType, String configName, T defaultData) {
		if (null == dataType) {
			return this.createConfigData(null, null, ConfigDataType.NotFound);
		}
		if (this.configSourceMapping.isEmpty()) {
			return this.createConfigData(null, null, ConfigDataType.NotFound);
		}
		ConfigName configNameObject = null;
		try {
			configNameObject = this.createConfigName(configName);
		} catch (Exception e) {
			logger.warn("create config name failed ({}#{})", dataType.getName(), configName);
			return this.createConfigData(null, null, ConfigDataType.NotFound);
		}

		try {
			String configData = this.searchConfigSourceValue(configNameObject);
			if (null == configData) {
				if (null != defaultData) {
					return this.createConfigData(configNameObject, defaultData, ConfigDataType.Argument);
				}
				if (configNameObject.hasActualConfigValue()) {
					T resultData = this.convertValue(dataType, configNameObject.getActualConfigValue());
					if (null != resultData) {
						return this.createConfigData(configNameObject, resultData, ConfigDataType.Expression);
					}
				}
				return this.createConfigData(configNameObject, null, ConfigDataType.NotFound);
			}
			T resultValue = this.convertValue(dataType, configData);
			return this.createConfigData(configNameObject, resultValue, ConfigDataType.Definition);
		} catch (Exception e) {
			logger.warn("get config data failed ({}#{})", dataType.getName(), configName);
			return this.createConfigData(null, null, ConfigDataType.NotFound);
		}
	}

	protected <T> T convertValue(Class<T> valueType, String value) {
		if (int.class.equals(valueType)) {
			try {
				return GenericUtils.parse(Integer.valueOf(value));
			} catch (Exception e) {
				return GenericUtils.parse(NULL_VALUE_INT);
			}
		} else if (short.class.equals(valueType)) {
			try {
				return GenericUtils.parse(Short.valueOf(value));
			} catch (Exception e) {
				return GenericUtils.parse(NULL_VALUE_SHORT);
			}
		} else if (long.class.equals(valueType)) {
			try {
				return GenericUtils.parse(Long.valueOf(value));
			} catch (Exception e) {
				return GenericUtils.parse(NULL_VALUE_LONG);
			}
		} else if (double.class.equals(valueType)) {
			try {
				return GenericUtils.parse(Double.valueOf(value));
			} catch (Exception e) {
				return GenericUtils.parse(NULL_VALUE_DOUBLE);
			}
		} else if (float.class.equals(valueType)) {
			try {
				return GenericUtils.parse(Float.valueOf(value));
			} catch (Exception e) {
				return GenericUtils.parse(NULL_VALUE_FLOAT);
			}
		} else if (boolean.class.equals(valueType)) {
			try {
				return GenericUtils.parse(Boolean.valueOf(value));
			} catch (Exception e) {
				return GenericUtils.parse(NULL_VALUE_BOOLEAN);
			}
		} else if (char.class.equals(valueType)) {
			try {
				return GenericUtils.parse(value.charAt(0));
			} catch (Exception e) {
				return GenericUtils.parse(NULL_VALUE_CHAR);
			}
		} else if (byte.class.equals(valueType)) {
			try {
				return GenericUtils.parse(Byte.parseByte(value));
			} catch (Exception e) {
				return GenericUtils.parse(NULL_VALUE_BYTE);
			}
		}
		if (this.configDataConverterMapping.containsKey(valueType)) {
			return GenericUtils.parse(this.configDataConverterMapping.get(valueType).convert(value));
		}
		return null;
	}

	protected String searchConfigSourceValue(ConfigName configName) {
		String fullConfigName = configName.getActualConfigName();
		if (configName.hasActualPrefixValue()) {
			fullConfigName = configName.getActualPrefixValue() + fullConfigName;
		}
		for (Entry<String, ConfigSource> entry : this.configSourceMapping.entrySet()) {
			ConfigSource configSource = entry.getValue();
			if (configName.hasActualConfigFile()) {// 如果指定的文件名
				if (configSource.getConfigFile().equals(configName.getActualConfigFile()) && configSource.hasConfigData(fullConfigName)) {
					return configSource.getConfigData(fullConfigName);
				}
			} else {// 未指定文件名,搜索第一个存在的属性
				if (configSource.hasConfigData(fullConfigName)) {
					return configSource.getConfigData(fullConfigName);
				}
			}
		}
		return null;
	}

	protected <T> ConfigData<T> createConfigData(ConfigName configName, T configData, ConfigDataType configType) {
		return new AbstractConfigData<T>(configData, configType);
	}

	protected String formatConfigSourceKey(ConfigSource configSource) {
		return this.formatConfigSourceKey(configSource.getConfigFile(), configSource.getConfigFileExtension());
	}

	protected String formatConfigSourceKey(String fileName, String fileExtension) {
		return String.format("%s.%s", fileName, fileExtension);
	}

	protected abstract ConfigName createConfigName(String configName) throws Exception;

}